package it.uniroma2.dsk;

import it.uniroma2.dtk.op.IdealOperation;
import it.uniroma2.util.math.ArrayMath;
import it.uniroma2.util.math.MatrixUtils;
import it.uniroma2.util.vector.RandomVectorGenerator;
import it.uniroma2.util.vector.VectorProvider;

import java.util.Arrays;

/**
 * @author Lorenzo Dell'Arciprete
 * Default implementation of DS interface using a recursive function
 * This class abstracts from the actual ideal composition function implementation.
 */
public abstract class DefaultAbstractDS<T> implements DS<T> {

	protected RandomVectorGenerator vectorProvider;
	protected IdealOperation op;
	
	protected int vectorSize;
	protected double lambda = 1;	// Lambda parameter
	
	/**
	 * @param randomOffset - random seed for vector generation and composition
	 * @param vectorsSize - the dimension of the desired vector space
	 * @param lambda - the value of the lambda decaying factor
	 * @param opImplementation: an implementation of the vector composition operation
	 * @throws Exception
	 */
	public DefaultAbstractDS(int randomOffset, int vectorsSize, double lambda, IdealOperation opImplementation) throws Exception {
		vectorProvider = new RandomVectorGenerator(vectorsSize, randomOffset);
		this.vectorSize = vectorsSize;
		this.lambda = lambda;
		op = opImplementation;
	}
	
	/**
	 * @param randomOffset - random seed for vector generation and composition
	 * @param vectorsSize - the dimension of the desired vector space
	 * @param lambda - the value of the lambda decaying factor
	 * @param opImplementationClass: a class implementing the vector composition operation
	 * @throws Exception
	 */
	public DefaultAbstractDS(int randomOffset, int vectorsSize, double lambda, Class<?> opImplementationClass) throws Exception {
		vectorProvider = new RandomVectorGenerator(vectorsSize, randomOffset);
		this.vectorSize = vectorsSize;
		this.lambda = lambda;
		if (!IdealOperation.class.isAssignableFrom(opImplementationClass))
			throw new Exception("Class "+opImplementationClass+" does not implement interface IdealOperation!");
		op = (IdealOperation) opImplementationClass.newInstance();
		op.initialize(vectorProvider);
	}
	
	public double[] ds(T[] s) {
		initializeStore(s);
		double[] result = MatrixUtils.uniformVector(vectorSize, 0);
		try {
			for (int i=0; i<s.length; i++)
				result = ArrayMath.sum(result, dRecursive(s, i));
		}
		catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}
	
	public double[] dsf(T[] s) throws Exception {
		try {
			int firstNotNull = 0;
			while (s[firstNotNull]==null)
				firstNotNull++;
			int lastNotNull = s.length-1;
			while (s[lastNotNull]==null)
				lastNotNull--;
			if (firstNotNull>0 || lastNotNull<s.length-1)
				s = Arrays.copyOfRange(s, firstNotNull, lastNotNull);
		}
		catch(ArrayIndexOutOfBoundsException e) {
			throw new Exception("Cannot compute DSF for an empty subsequence", e);
		}
		double[] result = vectorProvider.generateRandomVector(getObjectCode(s[s.length-1])); 
		for (int i=s.length-2; i>=0; i--)
			if (s[i] != null)
				result = op(vectorProvider.generateRandomVector(getObjectCode(s[i])), result);
		result = ArrayMath.scalardot(Math.pow(lambda, s.length), result);
		return result;
	}
	
	/**
	 * Recursive computation of function d(s_i) for character i of string s.
	 * @param s - the whole string
	 * @param i - current index
	 * @return d(s_i)
	 * @throws Exception 
	 */
	protected abstract double[] dRecursive(T[] s, int i) throws Exception;
	
	/**
	 * Initializes matrices, hashmaps, or any other store used during the computation of a Distributed String.
	 * This method is called once at the beginning of every ds(s) invocation.
	 * @param s - the input string for ds method
	 */
	protected abstract void initializeStore(T[] s);
	
	/**
	 * The vector composition function
	 */
	public double[] op(double[] v1, double[] v2) throws Exception {
		if (op == null)
			throw new Exception("Composition function class not initialized!");
		return op.op(v1, v2);
	}
	
	public Class<?> getIdealOperation() {
		return op.getClass();
	}
	
	public int getVectorSize() {return vectorSize;}
	
	public VectorProvider getVectorProvider() {return vectorProvider;}
	
	public double getLambda() {
		return lambda;
	}
	
	public void setLambda(double lambda) {
		this.lambda = lambda;
	}
	
	/**
	 * This method must return a code that should identify every distinct object of type T, while being the same
	 * for "equal" objects, even when they are different Java objects.
	 * The semantics is the same as the Java Object hashCode() method, which is in fact the default implementation.
	 * The method is exposed, however, to allow for custom implementations.
	 * @param object
	 * @return the object distinct code
	 */
	protected int getObjectCode(T object) {
		return object.hashCode();
	}
}
